// command used to compile this .cpp file into dlllibrary. But currently, do not use it anymore.
// g++ -fPIC -c cPythonQueryInterface.cc -o cPythonQueryInterface.o
// g++ -shared cPythonQueryInterface.o -o libpyTest

#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <cstdlib>
#include <iostream>

#include <stdio.h>
#include <stdlib.h>
#include <string>

#include "cPythonQueryInterface.h"


using namespace std;

typedef int (*Callback)(unsigned int, unsigned int, unsigned int, QUERY_RESULT*);

typedef int (*Callback2)(unsigned int, unsigned int, POSTING_RESULT*);

typedef struct LocalQueryProcessorArgs {

  LocalQueryProcessorArgs() :
    query_stop_words_list_file(NULL),
    query_algorithm_local(LocalQueryProcessor::kDefault),
    query_mode_local(LocalQueryProcessor::kInteractive),
    result_format_local(LocalQueryProcessor::kNormal){
  }

  ~LocalQueryProcessorArgs() {
  }

  IndexFiles index_files1;
  const char* query_stop_words_list_file;
  LocalQueryProcessor::QueryAlgorithm query_algorithm_local;
  LocalQueryProcessor::QueryMode query_mode_local;
  LocalQueryProcessor::ResultFormat result_format_local;

} LocalQueryProcessorArgs ;

IndexFiles ParseIndexNameForDifferentLanguageInterface(const char* index_name) {
  assert(index_name != NULL);

  const char* colon = strchr(index_name, ':');
  if (colon != NULL) {
    const char* dot = strchr(colon + 1, '.');
    if (dot != NULL && (dot - colon) > 1 && strlen(dot + 1) > 0) {
      int group_num, file_num;
      group_num = atoi(colon + 1);
      file_num = atoi(dot + 1);
      return IndexFiles(string(index_name, (colon - index_name)), group_num, file_num);
    } else {
      GetErrorLogger().Log("Invalid index name specified on command line.", true);
    }
  } else {
    return IndexFiles(index_name);
  }

  return IndexFiles();
}



class Foo{
    public:
		Foo(const char *indexFileDirectory, const char *defaultIndexPrefixFileName);
		//Foo();
		~Foo();

        // schedule the query processing
        void push(unsigned int id,unsigned int channel, char *arg, unsigned int top_k, Callback callback){

        	// For the variable results_, you have to give it some place to live in the main memory.
        	query_results_ = (QUERY_RESULT*) malloc(sizeof(QUERY_RESULT) * (top_k+1));

        	cout << endl;
        	cout << endl;
        	cout << endl;
        	cout << "Top_k parameter:" << top_k<< endl;
        	cout << "Search Query: " << arg << endl;

        	string queryContent = string(arg);

        	if(queryContent != ""){
        		// get 'top_k' or less results back
            	int m = get_query_results(queryContent, id, top_k);

                // return the results by calling the callback function.
            	// results for single query are ready
                callback(id, channel, m , query_results_);
        	}
        	else{
        		cout << "Ending mark received(Query Processing)." << endl;
        		callback(id, channel, 0 , NULL);
        	}
        }

        // schedule for the pruning project
        void testPush(unsigned int id,char *arg1, char *arg2, char *arg3, Callback2 callback2){
        	// For the variable results_, you have to give it some place to live in the main memory.
        	query_results_ = (QUERY_RESULT*) malloc(sizeof(QUERY_RESULT) * (1));
        	posting_results_ = (POSTING_RESULT*) malloc(sizeof(POSTING_RESULT) * (1));

        	cout << "Original Input Query: " << arg1 << endl;
        	string queryContent = string(arg1);
        	string semantics = string(arg2);
        	string mode = string(arg3);

        	if(queryContent != ""){
        		if(mode == "Interactive"){
        			get_inverted_indexes_interactive(id, queryContent,semantics,mode);
        		}
        		else if(mode == "Programmatic"){
        			int m = get_inverted_indexes_programmatic(id, queryContent,semantics,mode);
                    // return the results by calling the callback function.
                	// results for single query are ready
                    callback2(id, m , posting_results_);
        		}
        	}
        	else{
        		cout << "Ending mark received(Posting Processing)." << endl;
        		callback2(id, 0 , NULL);
        	}
        }

        void updateInvertedIndexesPrecomputedScores(char *queryTerm,char * docIdInStringFormat,char *scoreInStringFormat){
        	uint32_t docId = strtoul(string(docIdInStringFormat).c_str(), NULL, 0);
        	float score = atof(string(scoreInStringFormat).c_str());
        	query_processor_pointer_->UpdateInvertedIndexesPreComputedScores(string(queryTerm), docId, score);
        }


        void report(){
        	cout << "**********Overall Query Report Begins.**********" << endl;
        	query_processor_pointer_->GetOverallQueryProcessingStatistics();
        	cout << "**********Overall Query Report Ends.**********" << endl;
        	cout << "This ends the C++ code" << endl;
        }


    private:
        LocalQueryProcessor *query_processor_pointer_;
        QUERY_RESULT* query_results_;
        POSTING_RESULT* posting_results_;

        // This line may have some problems.
        LocalQueryProcessorArgs local_query_processor_args;

        //wei: Can I solve this problem using a simple solution?
        vector<string> URLFieldsForQueryResults_;
        vector<string> queryTermFieldsForPostingResults_;

        void get_inverted_indexes_interactive(unsigned int id, string queryContent,string semantics, string mode)
        {
        	query_processor_pointer_->ModifyInvertedIndexesPreComputedScores(id,queryContent,semantics, mode);
        }

        int get_inverted_indexes_programmatic(unsigned int id, string queryContent,string semantics, string mode)
        {
        	// Currently, this seems to be the ideal API.
        	// query_processor_pointer_->ModifyInvertedIndexesPreComputedScores(id,queryContent,semantics);



        	queryTermFieldsForPostingResults_.clear();


        	int numberOfPostingResultsReturned = query_processor_pointer_->ModifyInvertedIndexesPreComputedScores(id,queryContent,semantics,mode);
    		//numberOfPostingResultsReturned = query_processor_pointer_->posting_results_in_string().size();

    		cout << "numberOfPostingResultsReturned:" << numberOfPostingResultsReturned << endl;

        	// For the variable posting_results_, you have to give it some place to live in the main memory.

    		// old version
    		//posting_results_ = (POSTING_RESULT*) malloc(sizeof(POSTING_RESULT) * (numberOfPostingResultsReturned + 1));
    		//POSTING_RESULT* r = posting_results_;

    		// new version
    		posting_results_ = query_processor_pointer_->posting_results_ptr();

    		// for test.
    		/*
    		for(unsigned int tempCounter = 0; tempCounter < query_processor_pointer_->posting_results_in_string().size(); tempCounter++){
    			cout << query_processor_pointer_->posting_results_in_string()[tempCounter] << endl;
    		}
    		*/

    		// old version
    		/*
    		cout << "test in" << endl;
    		for(unsigned int tempCounter = 0; tempCounter < query_processor_pointer_->posting_results_in_string().size(); tempCounter++){
    			vector<string> elementsForEachLine;
    			boost::algorithm::split(elementsForEachLine, query_processor_pointer_->posting_results_in_string()[tempCounter], boost::algorithm::is_any_of(" ") );
        		queryTermFieldsForPostingResults_.push_back(elementsForEachLine[0]);
        		r->queryTerm = queryTermFieldsForPostingResults_[tempCounter].c_str();
        		r->docId = strtoul(elementsForEachLine[1].c_str(), NULL, 0);
        		r->score = atof(elementsForEachLine[2].c_str());
        		r++;
        		//cout << "tempCounter:" << tempCounter << endl;
    		}
    		cout << "test out" << endl;


    		// send 'k' or less results back
    		// null terminate the list
    		// need check
    		r->queryTerm = NULL;
    		r->docId = -1;
    		r->score = -1;
    		*/
    		return numberOfPostingResultsReturned;
        }

    	int get_query_results(string queryContent, unsigned int id,unsigned int top_k)
    	{
    		QUERY_RESULT* r = query_results_;
    		URLFieldsForQueryResults_.clear();
    		int numberOfResultsReturned = 0;
    		vector<string> resultsInString;
    		numberOfResultsReturned = query_processor_pointer_->ExecuteQuery(queryContent, id, resultsInString, top_k);

    		cout << "Returning " << resultsInString.size() << " results from local search engine black box." << endl;

    		for(unsigned int tempCounter = 0; tempCounter < resultsInString.size(); tempCounter++){
    			vector<string> elementsForEachLine;
    			boost::algorithm::split(elementsForEachLine, resultsInString[tempCounter], boost::algorithm::is_any_of(" ") );
        		r->score = atof(elementsForEachLine[0].c_str());
        		r->docId = strtoul(elementsForEachLine[1].c_str(), NULL, 0);
        		URLFieldsForQueryResults_.push_back(elementsForEachLine[2]);
        		r->url = URLFieldsForQueryResults_[tempCounter].c_str();
        		r++;
    		}
    		// send 'k' or less results back

    		// null terminate the list
    		// need check
    		r->score = -1;
    		r->docId = -1;
    		r->url = NULL;
    		return numberOfResultsReturned;
    	}
};

Foo::Foo(const char *indexFileDirectory, const char *defaultIndexPrefixFileName){
	//Foo::Foo(){
	//cout << "This begins the C++ code" << endl;
	local_query_processor_args.index_files1 = ParseIndexNameForDifferentLanguageInterface( defaultIndexPrefixFileName);
	local_query_processor_args.index_files1.SetDirectory( indexFileDirectory );

	cout << "Starting local query processor with index '" << local_query_processor_args.index_files1.prefix() << "'.";

	query_processor_pointer_ = new LocalQueryProcessor(local_query_processor_args.index_files1, local_query_processor_args.query_stop_words_list_file, local_query_processor_args.query_algorithm_local,
            local_query_processor_args.query_mode_local, local_query_processor_args.result_format_local,"auto");

}
Foo::~Foo(){
	//free(query_results_);
	//free(posting_results_);
	query_processor_pointer_ = NULL;
	query_results_ = NULL;
	posting_results_ = NULL;
}

extern "C" {
	//Foo* Foo_new(){
	Foo* Foo_new(char *indexFileDirectory, char *defaultIndexPrefixFileName){
        return new Foo(indexFileDirectory, defaultIndexPrefixFileName);
        // return new Foo();
    }

	void Foo_delete(Foo* foo) {
		delete foo;
	}

    void Foo_push(Foo* foo,unsigned int id, unsigned int channel, char *arg, unsigned int top_k, Callback callback){
        foo->push(id,channel,arg,top_k,callback);
    }

    void Foo_testPush(Foo* foo,unsigned int id, char *arg1, char *arg2, char *arg3, Callback2 callback){
        foo->testPush(id,arg1,arg2,arg3,callback);
    }

    void Foo_update_inverted_indexes_precomputed_scores(Foo* foo,char *queryTerm,char * docIdInStringFormat,char *scoreInStringFormat){
    	foo->updateInvertedIndexesPrecomputedScores(queryTerm,docIdInStringFormat,scoreInStringFormat);
    }
    void Foo_report(Foo* foo){
        foo->report();
    }
}

